package org.easylookandfeel.core;

import java.awt.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;

import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import org.easylookandfeel.adapter.LookAndFeelAdapter;

/**
 * Manages Look and Feel life cycle.
 * 
 * @author pcmnac@gmail.com
 * 
 */
public class LookAndFeelManager {

  /**
   * Stores installed Looks.
   */
  private UIManager.LookAndFeelInfo[] looks;

  /**
   * Adapters managed by this {@link LookAndFeelManager}.
   */
  private List<LookAndFeelAdapter> adapters = new ArrayList<LookAndFeelAdapter>();

  /**
   * Root component where look and feel changes will be applied to.
   */
  private Component rootComponent;

  /**
   * Indicates if this {@link LookAndFeelManager} will use the system's look
   * when there are no other available.
   */
  private boolean useSystemLookAndFeel;

  /**
   * Stores look and feel preferences.
   */
  private Preferences preferences;

  /**
   * Look and feel preferences key.
   */
  private static final String LOOK_AND_FEEL_PREFERENCES_KEY = "org.easylookandfeel.preferences.CURRENT_LOOK_AND_FEEL";

  /**
   * Invalid preferences look and feel value.
   */
  private static final int INVALID_LOOK_AND_FEEL_INDEX = -1;

  /**
   * Constructor.
   * 
   * @param rootComponent
   *          Root component where look and feel changes will be applied to.
   * @param useSystemLookAndFeel
   *          Indicates if this {@link LookAndFeelManager} will use the system's
   *          look.
   */
  public LookAndFeelManager(Component rootComponent,
      boolean useSystemLookAndFeel) {
    this.rootComponent = rootComponent;
    this.useSystemLookAndFeel = useSystemLookAndFeel;
    preferences = Preferences.userNodeForPackage(rootComponent.getClass());
  }

  /**
   * Gets installed look and feels and initialize adapters.
   * 
   */
  public void initialize() {

    looks = UIManager.getInstalledLookAndFeels();

    for (LookAndFeelAdapter adapter : adapters) {
      try {
        adapter.initializeLookAndFeel(looks);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    int lookAndFeelIndex = INVALID_LOOK_AND_FEEL_INDEX;

    int preferencesLookAndFeel = preferences.getInt(
        LOOK_AND_FEEL_PREFERENCES_KEY, INVALID_LOOK_AND_FEEL_INDEX);

    if ((preferencesLookAndFeel == INVALID_LOOK_AND_FEEL_INDEX)
        && useSystemLookAndFeel) {
      // Gets System Look and Feel.
      String systemLookAndFeelClassName = UIManager
          .getSystemLookAndFeelClassName();
      for (int i = 0; i < looks.length; i++) {
        if (looks[i].getClassName().equals(systemLookAndFeelClassName)) {
          lookAndFeelIndex = i;
          break;
        }
      }
    } else {
      lookAndFeelIndex = preferencesLookAndFeel;
    }

    changeLookAndFeel(lookAndFeelIndex);
  }

  /**
   * Reloads and apply looks.
   */
  public void reload() {
    initialize();
  }

  /**
   * Returns available looks.
   * 
   * @return Available looks.
   */
  public UIManager.LookAndFeelInfo[] getAvailableLooks() {
    return looks;
  }

  /**
   * Changes current look and feel based on given index. This index must be
   * valid in {@link #looks} array. This method does not throw any exception
   * whether index is invalid or even if a exception has occurred during look
   * and feel changing.
   * 
   * @param lookAndFeelIndex
   *          The index of chosen Look in {@link #looks} array.
   */
  public void changeLookAndFeel(int lookAndFeelIndex) {
    try {
      UIManager.setLookAndFeel(looks[lookAndFeelIndex].getClassName());
      SwingUtilities.updateComponentTreeUI(rootComponent);

      for (LookAndFeelAdapter adapter : adapters) {
        try {
          adapter.handleLookAndFeelChanged(lookAndFeelIndex);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }

      preferences.putInt(LOOK_AND_FEEL_PREFERENCES_KEY, lookAndFeelIndex);
    } catch (Exception e) {
    }
  }

  /**
   * Adds a {@link LookAndFeelAdapter} to this manager. This method calls
   * {@link LookAndFeelAdapter#setManager(LookAndFeelManager)} on the given
   * adaper.
   * 
   * @param adapter
   *          The {@link LookAndFeelAdapter} to be added.
   */
  public void addAdapter(LookAndFeelAdapter adapter) {
    adapter.setManager(this);
    adapters.add(adapter);
  }
  
  /**
   * Removes a {@link LookAndFeelAdapter} from this manager.
   * 
   * @param adapter
   *          The {@link LookAndFeelAdapter} to be removed.
   */
  public void removeAdapter(LookAndFeelAdapter adapter) {
    adapters.remove(adapter);
  }
}
